/*
 * @author: wizardc
 */

using System.Collections.Generic;
using UnityEngine;

namespace Dou.Pathfinding
{
    /// <summary>
    /// 基于正方形的 A 星寻路格子描述
    /// </summary>
    public class AStarGrid : IAStarGraph
    {
        private static readonly float STRAIGHT_COST = 1;
        private static readonly float DIAG_COST = 1.4f;
        
        /**
         * 曼哈顿启发函数
         */
        private static float Manhattan(AStarGridNode node1, AStarGridNode node2)
        {
            var dx = node1.x > node2.x ? node1.x - node2.x : node2.x - node1.x;
            var dy = node1.y > node2.y ? node1.y - node2.y : node2.y - node1.y;
            return (dx + dy) * STRAIGHT_COST;
        }

        /**
         * 欧式启发函数
         */
        private static float Euclidean(AStarGridNode node1, AStarGridNode node2)
        {
            var dx = node1.x > node2.x ? node1.x - node2.x : node2.x - node1.x;
            var dy = node1.y > node2.y ? node1.y - node2.y : node2.y - node1.y;
            return (dx * dx + dy * dy) * STRAIGHT_COST;
        }

        /**
         * 对角启发函数
         */
        private static float Diagonal(AStarGridNode node1, AStarGridNode node2)
        {
            var dx = node1.x > node2.x ? node1.x - node2.x : node2.x - node1.x;
            var dy = node1.y > node2.y ? node1.y - node2.y : node2.y - node1.y;
            return dx > dy ? DIAG_COST * dy + STRAIGHT_COST * (dx - dy) : DIAG_COST * dx + STRAIGHT_COST * (dy - dx);
        }
        
        protected AStarGridNode[,] _grid;
        protected int _cols;
        protected int _rows;
        protected int _version;
        protected AStarGridNode _startNode;
        protected AStarGridNode _endNode;

        public AStarGrid(int cols, int rows)
        {
            _cols = cols;
            _rows = rows;
            _grid = new AStarGridNode[_cols, _rows];
            for (int c = 0; c < _cols; c++)
            {
                for (int r = 0; r < _rows; r++)
                {
                    _grid[c, r] = new AStarGridNode { x = c, y = r };
                }
            }
            CacheAroundLinks();
        }
        
        public int cols => _cols;
        public int rows => _rows;
        
        private void CacheAroundLinks()
        {
            List<AStarLink> links = new List<AStarLink>();
            for (int c = 0; c < _cols; c++)
            {
                for (int r = 0; r < _rows; r++)
                {
                    var node = _grid[c, r];
                    var startX = Mathf.Max(0, node.x - 1);
                    var endX = Mathf.Min(this._cols - 1, node.x + 1);
                    var startY = Mathf.Max(0, node.y - 1);
                    var endY = Mathf.Min(this._rows - 1, node.y + 1);
                    for (int m = startX; m <= endX; m++)
                    {
                        for (int n = startY; n <= endY; n++)
                        {
                            var test = _grid[m, n];
                            if (test == node || !test.walkable || !_grid[node.x, test.y].walkable || !_grid[test.x, node.y].walkable) {
                                continue;
                            }
                            var cost = STRAIGHT_COST;
                            if (!(node.x == test.x || node.y == test.y))
                            {
                                cost = DIAG_COST;
                            }
                            links.Add(new AStarLink { node = node, cost = cost });
                        }
                    }
                    node.aroundLinks = links.ToArray();
                    links.Clear();
                }
            }
        }

        public float GetCost(AStarNode node1, AStarNode node2)
        {
            return Manhattan((AStarGridNode) node1, (AStarGridNode) node2);
        }

        public int GetVersion()
        {
            return _version;
        }

        public void UpdateVersion()
        {
            _version++;
        }

        public AStarNode GetStartNode()
        {
            return _startNode;
        }

        public AStarNode GetEndNode()
        {
            return _endNode;
        }
        
        /**
         * 设置是否可以通行，默认都不可通行
         */
        public void SetWalkable(int x, int y, bool value)
        {
            _grid[x, y].walkable = value;
        }

        public bool GetWalkable(int x, int y)
        {
            return _grid[x, y].walkable;
        }

        public void SetStartNode(int x, int y)
        {
            _startNode = _grid[x, y];
        }

        public void SetEndNode(int x, int y)
        {
            _endNode = _grid[x, y];
        }
    }

    public class AStarGridNode : AStarNode
    {
        public int x;
        public int y;
    }
}
